//
//  main.m
//  cmdDemo
//
//  Created by JIENING ZHANG on 2022/10/28.
//  Copyright © 2022 JIENING ZHANG. All rights reserved.
//

#import <Foundation/Foundation.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>

#import "NSDictionary+GetMethod.h"
#import "NSObject+GGJson.h"
#import "NSString+GGJson.h"

//https://blog.csdn.net/qq_43533553/article/details/120033104
int pipeTest(void) {
    //创建两个无名管道
    int pipefd1[2] = {0};
    int pipefd2[2] = {0};
    
    //pipe函数创建了一个单向数据通道，这个通道可以用来在进程之间通信。
    //pipefd: pipefd数组用来返回两个文件描述符，这两个文件描述符代表了通道的两端。pipefd[0]代表通道的读取端，pipefd[1]代表了通道的写入段。在读取端读取数据，在输写入端口写入数据。
    //返回值 函数调用成功返回0 调用失败返回-1，这时errno存储错误码。
    int pipe_father = pipe(pipefd1);
    int pipe_son = pipe(pipefd2);
    if (0 != pipe_father || 0 != pipe_son) {
        NSLog(@"fail to create pipes");
        return -1;
    }
    
    pid_t ret = fork(); //创建进程
    if (ret == -1)
    {
        perror("fail to fork");
    } else if (ret == 0) {
        //子进程
        char msg_son[32] = {0};
        char msg_father[32] = {0};
        
        while (1)
        {
            memset(msg_father, 0, 32);
            read(pipefd2[0], msg_father, 32);   //读阻塞，当管道没有数据时，对管道进行读取会阻塞
            printf("子进程，收到父进程消息:%s\n", msg_father);
            if (0 == strcmp(msg_father, "quit")) {
                write(pipefd1[1], "quit", strlen("quit"));
                printf("子进程，recieve quit, break runloop\n");
                break;
            }
            
            printf("子进程，儿子 消息输入(输入quit结束)：");
            scanf("%s", msg_son);
            write(pipefd1[1], msg_son, strlen(msg_son));
            
            if (0 == strcmp(msg_son, "quit")) {
                printf("子进程，send quit, break runloop\n");
                sleep(1);
                break;
            }
        }
        close(pipefd1[1]);
        close(pipefd2[0]);
        
        printf("子进程，about quit return 0\n");
        return 0;
        
    } else {
        
        //父进程
        char msg_father[32] = {0};
        char msg_son[32] = {0};
        while (1)
        {
            printf("父进程，消息输入(输入quit结束)：");
            scanf("%s", msg_father);
            write(pipefd2[1], msg_father, strlen(msg_father));
            if (0 == strcmp(msg_father, "quit")) {
                sleep(1);
                printf("父进程，send quit, break runloop\n");
                break;
            }
            
            memset(msg_son, 0, 32);
            read(pipefd1[0], msg_son, 32);
            printf("父进程，收到子进程消息:%s\n", msg_son);
            if (0 == strcmp(msg_son, "quit")) {
                printf("父进程，recieve quit, break runloop\n");
                break;
            }
        }
        
        close(pipefd2[1]);
        close(pipefd1[0]);
        //wait(NULL);
    }
    printf("父进程，about quit return 0\n");
    return 0;
}

#define EmptyString(aString) (nil == aString || 0 == [aString length])
#define NotEmptyString(aString) (nil != aString && 0 < [aString length])

#define EmptyDicOrArray(aobj) (nil == aobj || 0 == [aobj count])
#define NotEmptyDicOrArray(aobj) (nil != aobj && 0 < [aobj count])

#define NotNilString(aString) (aString ?: @"")

// @weakify & @strongify support
#define weakify(var) ext_keywordify __weak typeof(var) O2OWeak_##var = var;
#define strongify(var) ext_keywordify \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
__strong typeof(var) var = O2OWeak_##var; \
_Pragma("clang diagnostic pop")
#if DEBUG
#   define ext_keywordify autoreleasepool {}
#else
#   define ext_keywordify try {} @catch (...) {}
#endif

#define BUFSIZESmall (1024)
#define BUFSIZE (4096)
#define BUFSIZELarge (4096*4)

void printHelp(void) {
    printf("function List: \n");
    printf("-rp :   runner socket 端口号\n");
    printf("-c  :   作为主控程序启动\n");
    printf("-cn :   主控程序启动 新建n个沙盒目录\n");
}

int loop2ViaSocket(NSMutableArray *taskArr, NSDictionary* callArgs) {
    if EmptyDicOrArray(callArgs) {
        return -1;
    }
    
    int loop=0;
    char buf[BUFSIZE] = {0};
    char bufWrite[BUFSIZESmall] = {0};
    
    for (NSMutableDictionary *taskInfo in taskArr) {
        int sockfd = (int)[taskInfo validIntegerValue:@"sockfd"];
        
        memset(bufWrite, 0, sizeof(bufWrite));
        NSString *jsonStr = [callArgs ggJsonString];
        strcpy(bufWrite, jsonStr.UTF8String);
        if(send(sockfd, bufWrite, strlen(bufWrite), 0) == -1 )
        {
            printf("Controler: --> socket %d fail: %s\n", sockfd , bufWrite);
            return -1;
        }
        printf("Controler: --> socket %d succ: %s\n", sockfd , bufWrite);
        
        //接收服务器回传的信息
        memset(buf, 0, sizeof(buf));
        if(recv(sockfd, buf, sizeof(buf) - sizeof(buf[0]), 0) == -1)
        {
            printf("Controler: <-- socket %d fail: %s\n", sockfd , buf);
            return -1;
        }
        printf("Controler: <-- socket %d succ: %s\n", sockfd , buf);
        taskInfo[@"msgFromRunner"] = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
        ++loop;
    }
    
    return 0;
}

int loop2InitViaSocket(NSMutableArray *taskArr) {
    if (0 != loop2ViaSocket(taskArr, @{@"cmd":@"sdkInit"})) {
        return -1;
    }
    for (NSMutableDictionary *taskInfo in taskArr) {
        if (! [[taskInfo validStringValue:@"msgFromRunner"] isEqualToString:@"succ"]) {
            NSLog(@"Controler: sdk init fail %@",taskInfo);
            return -1;
        }
    }
    return 0;
}

int loop2Quit(NSMutableArray *taskArr) {
    char buf[BUFSIZE] = {0};
    char bufWrite[BUFSIZESmall] = {0};
    for (NSMutableDictionary *taskInfo in taskArr) {
        int sockfd = (int)[taskInfo validIntegerValue:@"sockfd"];
        
        memset(bufWrite, 0, sizeof(bufWrite));
        memset(buf, 0, sizeof(buf));

        strcpy(bufWrite, "quit");
        if(send(sockfd, bufWrite, strlen(bufWrite), 0) == -1 )
        {
            printf("Controler: --> socket %d fail: %s\n", sockfd , bufWrite);
            return -1;
        }
        printf("Controler: --> socket %d succ: %s\n", sockfd , bufWrite);
        
        //接收服务器回传的信息
        if(recv(sockfd, buf, sizeof(buf) - sizeof(buf[0]), 0) == -1)
        {
            printf("Controler: <-- socket %d fail: %s\n", sockfd , buf);
            return -1;
        }
        printf("Controler: <-- socket %d succ: %s\n", sockfd , buf);
    }
    
    return 0;
}

void printControllerHelp(void) {
    printf("case List: \n");
    printf("1 :   注册\n");
    printf("q :   退出\n\n 请选择:");
}

int loop2isRegist(NSMutableArray *taskArr) {
    return loop2ViaSocket(taskArr, @{@"cmd":@"queryIsRegisted"});
}

int loop2ControlViaSocket(NSMutableArray *taskArr) {
    if (0 != loop2InitViaSocket(taskArr)) {
        return -1;
    }
    
    //char buf[BUFSIZE] = {0};
    //char bufWrite[BUFSIZE] = {0};
    while(1) {
        printControllerHelp();
        
        char chooseInput[32] = {0};
        scanf("%s", chooseInput);
        printf("\n");
        
        if (0 == strcmp("q", chooseInput)) {
            loop2Quit(taskArr);
            break;
        } else if (0 == strcmp("1", chooseInput)) {
            
            loop2isRegist(taskArr);  // 是否注册
        }
    }
    
    for (NSMutableDictionary *taskInfo in taskArr) {
        int sockfd = (int)[taskInfo validIntegerValue:@"sockfd"];
        //关闭套接字
        
        printf("Controler: 关闭sockfd %d\n", sockfd);
        close(sockfd);
    }
    return 0;
}

int loop2LaunchTask(NSMutableArray *taskArr) {
    // https://blog.csdn.net/lovechris00/article/details/78145937
    for (NSMutableDictionary *taskInfo in taskArr) {
        
        NSArray* launchArgs = @[@"-rp",
                                [taskInfo validStringValue:@"socketPort"]];
        
        NSLog(@"task launch args: %@\n", launchArgs);
        NSString *runnerTaskFilePath = [taskInfo validStringValue:@"launchPath"];
        
        // NSTask类只有Mac OS程序开发才有，使用NSTask类，你的程序可以执行另一个程序并获取程序运行的结果。
        // 注意，NSTask创建的是一个独立运行的进程，不会与主程序共享存储空间。与 fork 出来的子进程不同
        NSTask *task;
        task = [[NSTask alloc] init];
        [task setLaunchPath: runnerTaskFilePath];
        //NSString *commandStr = @"last | grep reboot";
        //NSArray *arguments = [NSArray arrayWithObjects:@"-c",commandStr,nil];
        //NSLog(@"arguments : %@",arguments);
        [task setArguments: launchArgs];
        
        [task launch];
        
        taskInfo[@"taskObj"] = task;
    }
    return 0;
}

int loop2ConnetSocket(NSMutableArray *taskArr) {
    for (NSMutableDictionary *taskInfo in taskArr) {
        //创建服务器套接字
        printf("Controler：创建套接字\n");
        int sockfd = socket(AF_INET,SOCK_STREAM,0);
        if(sockfd ==-1)
        {
            printf("Controler: sockfd fail\n");
            return -1;
        }
        printf("Controler: sockfd:%d\n",sockfd);
        
        int runnerSocketPort = (int)[taskInfo validIntegerValue:@"socketPort"];
        
        //组织服务器的地址结构
        //printf("客户端：组织服务器的地址结构\n");
        struct sockaddr_in ser;
        ser.sin_family = AF_INET;
        ser.sin_port = htons(runnerSocketPort);
        ser.sin_addr.s_addr = inet_addr("127.0.0.1");//不能像服务器宏一样
        //发起连接
        printf("Controler: 发起连接\n");
        if(connect(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1 )
        {
            printf("Controler: connect fail, sockfd:%d\n", sockfd);
            return -1;
        }
        printf("Controler: connect succ, sockfd:%d\n",sockfd);

        taskInfo[@"sockfd"] = @(sockfd);
    }
    return 0;
}


int controllerAsSocketClient(const char* runnerTaskFilePath ,int sandBoxCreateNum) {
    printf("Controler begin \n");
    
    if (sandBoxCreateNum < 1) {
        printf("Controler: invalid sandBoxCreateNum %d \n", sandBoxCreateNum);
        return -1;
    }
    
    NSMutableArray *taskArr = [NSMutableArray array];
    int runnerPort = 30086;
    for (int loop=0; loop<sandBoxCreateNum; ++loop) {
        
        NSString *runnerNameStr = [NSString stringWithFormat:@"user_%d",loop];
        
        [taskArr addObject: [NSMutableDictionary dictionaryWithDictionary: @{
            @"runnerName":  runnerNameStr,
            @"launchPath" : [NSString stringWithCString:runnerTaskFilePath encoding:NSUTF8StringEncoding],
            @"socketPort" : @(runnerPort),
        }]];
        
        runnerPort += 1;
    }
    
    // 通过 NSTask 启动n个runner 进程，runner进程开启socket 做server，等待 controller 开启socket 做client 向其发送指令，获取返回值
    NSLog(@"taskArr before loop2LaunchTask, %@", taskArr);
    loop2LaunchTask(taskArr);
    NSLog(@"taskArr after loop2LaunchTask, %@", taskArr);
    sleep(2);
    
    
    if(0 != loop2ConnetSocket(taskArr)){
        printf("Controler: loop2ConnetSocket fail\n");
        return -1;
    }
    NSLog(@"taskArr after loop2ConnetSocket, %@", taskArr);
    
    loop2ControlViaSocket(taskArr);
    
    return 0;
}

int runnerSendViaSocket(const char*runnerName, NSString *msg, int conn) {
    char* cStr = (char*)("empty");
    if NotEmptyString(msg) {
        cStr = (char*)(msg.UTF8String);
    }

    if(write(conn, cStr, strlen(cStr)) == -1)
    {
        printf("runner %s: write into socket fail, %s \n", runnerName, cStr);
        return -1;
    }
    //printf("runner %s: write into socket succ, %s \n", runnerName, cStr);
    
    return 0;
}

int runnerAsSocketSever(int runnerSocketPort) {
    // https://blog.csdn.net/m0_53311279/article/details/125317083
    const char* runnerName = [[@"user_" stringByAppendingFormat:@"_%d"] cStringUsingEncoding:NSUTF8StringEncoding];
    //创建侦听套接字
    printf("runner %s:create socket\n", runnerName);
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);//返回侦听套接字
    if(sockfd == -1)
    {
        printf("runner %s:create sockfd fail\n", runnerName);
        return -1;
    }
    printf("runner %s:sockfd:%d\n", runnerName,sockfd);
    //组织地址结构，代表服务器
    //printf("服务器：组织地址结构\n");
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(runnerSocketPort);//注意大小端问题，存是小端，服务器大端方式拿。
    //ser.sin_addr.s_addr = inet_addr("127.0.0.1")
    ser.sin_addr.s_addr = INADDR_ANY;//表示接受任意IP下的地址
    //绑定套接字和地址结构
    //printf("服务器：绑定套接字和地址结构\n");
    int sockbind = bind(sockfd, (struct sockaddr*)&ser, sizeof(ser));
    if(sockbind == -1)
    {
        printf("runner %s:bind socket fail, port %d sockfd %d\n", runnerName, runnerSocketPort, sockfd);
        return -1;
    }
    printf("runner %s:bind bind socket succ, sockbind : %d\n", runnerName,sockbind);
    
    //开启侦听功能
    //printf("服务器：开启侦听功能\n");
    int socklisten = listen(sockfd, BUFSIZE);
    if(socklisten == -1)
    {
        printf("runner %s: listen socket fail, port %d sockfd %d\n", runnerName, runnerSocketPort, sockfd);
        return -1;
    }
    printf("runner %s: listen socket succ, socklisten : %d\n", runnerName, socklisten);
    
    struct sockaddr_in cli;//用来输出客户端的地址结构
    socklen_t len = sizeof(cli);
    int conn = accept(sockfd,(struct sockaddr*)&cli,&len);//返回后续用来通信的通信套接字
    if(conn == -1)
    {
        printf("runner %s: accept socket fail, socklisten : %d\n", runnerName, socklisten);
        return -1;
    }
    printf("runner %s: accept socket succ, clientAddr:%s , clientPort:%d \n", runnerName, inet_ntoa(cli.sin_addr), ntohs(cli.sin_port));
    
    char buf[BUFSIZESmall] = {0};
    char bufWrite[BUFSIZE] = {0};
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        ssize_t size = read(conn, buf, sizeof(buf) - sizeof(buf[0]));
        if(size == -1)
        {
            printf("runner %s: read from socket fail\n", runnerName);
            //perror("runner read from socket fail\n");
            return -1;
        }
        
        if(size == 0)
        {
            printf("runner %s: 客户端断开连接\n", runnerName);
            break;
        }
        
        //printf("runner %s: read from socket, %s \n", runnerName, buf);
        
        memset(bufWrite, 0, sizeof(bufWrite));
        if (0 == strcmp(buf, "quit")) {
            sprintf(bufWrite, "我顶你个肺！");
        } else {
            // 业务逻辑搞起
            NSString* jsonStr = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
            NSDictionary *dicFromJson = [jsonStr ggJsonObj];
            if (! dicFromJson) {
                break;
            }
            NSString *cmd = [dicFromJson validStringValue:@"cmd"];
            if ([cmd isEqualToString:@"sdkInit"]) {
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    runnerSendViaSocket(runnerName, @"succ", conn);
                });
                continue;
            } else if ([cmd isEqualToString:@"queryIsRegisted"]){
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    runnerSendViaSocket(runnerName, @"succ", conn);
                });
                continue;
            } else {
                sprintf(bufWrite, "invalid cmd");
            }
        }
        
        //发送给客户端
        if(write(conn, bufWrite, strlen(bufWrite)+1) == -1)
        {
            printf("runner %s: write into socket fail, %s \n", runnerName, buf);
            return -1;
        }
        printf("runner %s: write into socket succ, %s \n", runnerName, buf);
        
        if (0 == strcmp(buf, "quit")) {
            break;
        }
    }
    
    //关闭套接字
    printf("runner %s: 关闭conn \n", runnerName);
    close(conn);
    printf("runner %s: 关闭sockfd \n", runnerName);
    close(sockfd);

    
    return 0;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 基于fork 和 无名管道的 父子进程间通信
        //pipeTest();
        
        // 基于 NSTask 和 socket 的父子进程间通信(通信使用json)
        
        // argv[0] 当前程序对应的可执行文件全路径
        printf("arg0: %s\n",argv[0]);
        
        BOOL runAsControler = false;
        int runnerSocketPort = -1;
        int sandBoxCreateNum = 0;
        
        for (int loop=1; loop<argc; ++loop) {
            if (0 == strcmp("-c", argv[loop])) {
                runAsControler = true;
            }
            
            if (0 == strcmp("-cn", argv[loop])) {
                if (loop < argc-1) {
                    sandBoxCreateNum = atoi(argv[loop+1]);
                }
            }
            
            if (0 == strcmp("-rp", argv[loop])) {
                if (loop < argc-1) {
                    runnerSocketPort = atoi(argv[loop+1]);
                }
            }
        }
        
        if (runAsControler) {
            // 主控, 模拟创建多个用户（socket server）, 和 这些用户通信（通过 socket client）
            controllerAsSocketClient(argv[0], sandBoxCreateNum);
            
        } else {
            
            // 测试沙盒
            if (runnerSocketPort<=0){
                printf("\nrunner，没有有效的socket 端口号\n\n");
                printHelp();
                return -1;
            }
            
            printf("\nrunner，socket 端口号 %d\n", runnerSocketPort);
            
            runnerAsSocketSever(runnerSocketPort);
        }
    }
    return 0;
}
